import abc
import datetime
import functools

from coin.exchange.base.kr_rest.currency import NormCurrency, NativeCurrency
from coin.exchange.base.kr_rest.from_str import FromStrProductMixin


def gen_short_symbol(base, quote):
  short_symbol_tmpl = "{base}-{quote}"
  assert isinstance(base, (NormCurrency, str))
  assert isinstance(quote, (NormCurrency, str))

  return short_symbol_tmpl.format(
      base=base.symbol if isinstance(base, NormCurrency) else base,
      quote=quote.symbol if isinstance(quote, NormCurrency) else quote,
  )


def gen_full_symbol(base, quote, exchange, native_symbol):
  full_symbol_tmpl = "{base}-{quote}.{exchange}.{native_symbol}"
  assert isinstance(base, (NormCurrency, str))
  assert isinstance(quote, (NormCurrency, str))

  return full_symbol_tmpl.format(
      base=base.symbol if isinstance(base, NormCurrency) else base,
      quote=quote.symbol if isinstance(quote, NormCurrency) else quote,
      exchange=exchange,
      native_symbol=native_symbol,
  )


@functools.total_ordering
class NormProduct(object):
  def __init__(self, base, quote):
    assert isinstance(base, (NormCurrency, str))
    assert isinstance(quote, (NormCurrency, str))
    assert base != quote, (base, quote)
    self._base = base
    self._quote = quote
    self._short_symbol = gen_short_symbol(self.base, self.quote)
    self._subscription_symbol = self._short_symbol

  def __str__(self):
    return self._short_symbol

  def __repr__(self):
    return repr(dict(base=self.base, quote=self.quote, symbol=self.symbol))

  def __hash__(self):
    return hash(str(self))

  def __eq__(self, other):
    if self is other:
      return True
    if isinstance(other, NormProduct):
      if type(self) == type(other):
        return str(self) == str(other)
      else:
        return False
    elif type(other) == str:
      return str(self) == other or self.symbol == other
    else:
      other = str(other)
      return str(self) == other or self.symbol == other

  def __lt__(self, other):
    assert isinstance(other, NormProduct), type(other)
    return str(self) < str(other)

  @property
  def base(self):
    return self._base

  @property
  def quote(self):
    return self._quote

  @property
  def short_symbol(self):
    return self._short_symbol

  @property
  def subscription_symbol(self):
    return self._subscription_symbol

  @property
  def symbol(self):
    return self._short_symbol


class NativeProduct(NormProduct, FromStrProductMixin):
  __metaclass__ = abc.ABCMeta

  _product_type = "Spot"
  _exchange = "UNKNOWN"
  NativeCurrencyType = None

  def __init__(self, base, quote, native_symbol):
    assert isinstance(base, (NativeCurrency, str))
    assert isinstance(quote, (NativeCurrency, str))
    assert base != quote, (base, quote)
    if self.NativeCurrencyType is not None:
      assert isinstance(base, self.NativeCurrencyType), (type(base), self.NativeCurrencyType)
      assert isinstance(quote, self.NativeCurrencyType), (type(quote), self.NativeCurrencyType)

    super().__init__(base, quote)
    assert isinstance(native_symbol, str), native_symbol

    self._native_symbol = native_symbol
    self._native_subscription_symbol = native_symbol
    self._full_symbol = gen_full_symbol(self.base, self.quote, self.exchange, self.native_symbol)

  def __str__(self):
    return self._full_symbol

  def __repr__(self, *args):
    append_args = ''
    if args:
      append_args = ', ' + ', '.join(args)
    return "%sProduct(%s, %s%s)" % (
        self.exchange, self.short_symbol, self.native_symbol, append_args)

  @property
  def product_type(self):
    return type(self)._product_type

  @property
  def exchange(self):
    return type(self)._exchange

  @property
  def native_symbol(self):
    return self._native_symbol

  @property
  def native_subscription_symbol(self):
    return self._native_subscription_symbol

  @property
  def full_symbol(self):
    return self._full_symbol

  def assert_component(self, base, quote, symbol, native_symbol):
    assert self.base == base, (self.base, base)
    assert self.quote == quote, (self.quote, quote)
    assert self.symbol == symbol, (self.symbol, symbol)
    assert self.native_symbol == native_symbol, (self.native_symbol, native_symbol)

  def assert_component2(self, base, quote, symbol, exchange, native_symbol):
    assert self.base == base, (self.base, base)
    assert self.quote == quote, (self.quote, quote)
    assert self.exchange == exchange, (self.exchange, exchange)
    assert self.symbol == symbol, (self.symbol, symbol)
    assert self.native_symbol == native_symbol, (self.native_symbol, native_symbol)

  @classmethod
  def FromProduct(cls, product, current_datetime=None):
    # assert isinstance(product, NormProduct), (type(product), product)
    current_datetime = current_datetime or datetime.datetime.now()
    base = cls.NativeCurrencyType.FromStrCurrency(product.base.currency, current_datetime)
    quote = cls.NativeCurrencyType.FromStrCurrency(product.quote.currency, current_datetime)
    return cls(base, quote)

  @classmethod
  def FromStrPair(cls, base, quote, current_datetime=None):
    assert isinstance(base, str), (type(base), base)
    assert isinstance(quote, str), (type(quote), quote)
    current_datetime = current_datetime or datetime.datetime.now()
    base = cls.NativeCurrencyType.FromStrCurrency(base, current_datetime)
    quote = cls.NativeCurrencyType.FromStrCurrency(quote, current_datetime)
    return cls(base, quote)

  @classmethod
  def FromStrPairNative(cls, base, quote, current_datetime=None):
    assert isinstance(base, str), (type(base), base)
    assert isinstance(quote, str), (type(quote), quote)
    current_datetime = current_datetime or datetime.datetime.now()
    base = cls.NativeCurrencyType.FromStrNativeCurrency(base, current_datetime)
    quote = cls.NativeCurrencyType.FromStrNativeCurrency(quote, current_datetime)
    return cls(base, quote)

  @classmethod
  def FromStrProductImpl(cls, product, current_datetime):
    assert isinstance(product, str), (type(product), product)
    assert "-" in product
    pair = product.split("-")
    assert len(pair) == 2
    base = cls.NativeCurrencyType.FromStrCurrency(pair[0], current_datetime)
    quote = cls.NativeCurrencyType.FromStrCurrency(pair[1], current_datetime)
    return cls(base, quote)

  @classmethod
  def FromStrNativeProductImpl(cls, product, current_datetime):
    assert isinstance(product, str), (type(product), product)
    assert "-" in product
    pair = product.split("-")
    assert len(pair) == 2
    base = cls.NativeCurrencyType.FromStrNativeCurrency(pair[0], current_datetime)
    quote = cls.NativeCurrencyType.FromStrNativeCurrency(pair[1], current_datetime)
    return cls(base, quote)
